import asyncio
import logging
import uvloop
import socketio
import sys

from threading import Thread, Event
import websockets
from websockets import WebSocketClientProtocol

from tools import aio_redis_pool

# from app import SioApp
from typing import Union


logger = logging.getLogger(__name__)
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

class ContainerAgentThread(Thread):

    def __init__(self, container_uuid, vm_uuid, namespace, *args, sid="", ws_url="", queue_url="", **kwargs):
        super(ContainerAgentThread, self).__init__(*args, **kwargs)
        self.container_uuid = container_uuid
        self.vm_uuid = vm_uuid
        self.namespace = namespace
        self.ws_url = ws_url
        self.queue_url = queue_url
        self.sid = sid
        self.room = f"{sid}::{vm_uuid}"
        self.loop = None

        self._running = Event()
        stop_flag = f"{sid}:{container_uuid}::{vm_uuid}::STOP"
        self.stop_flag = stop_flag.encode()
        self.external_sio: Union[None, socketio.asyncio_server.AsyncServer] = None

    async def listen_terminal(self, ws: WebSocketClientProtocol):
        channel = await aio_redis_pool.pubsub()
        await channel.subscribe(f"container_channel::{self.sid}::{self.vm_uuid}")

        while True:
            data = await channel.get_message(ignore_subscribe_messages=True)
            if data is not None:
                msg = data["data"]
                if msg == self.stop_flag:
                    sys.exit()
                await ws.send(msg)
            else:
                pass

    async def listen_server(self, ws: WebSocketClientProtocol):
        while True:
            greeting = await ws.recv()
            await self.external_sio.emit(
                "from_container_data", greeting.decode(),
                namespace=self.namespace,
                to=self.room
            )

    async def ws_client(self):
        async with websockets.connect(self.ws_url, subprotocols=["binary", "base64"]) as ws:
            await asyncio.gather(
                self.listen_terminal(ws),
                self.listen_server(ws),
                loop=self.loop,
                return_exceptions=True
            )

    def run(self) -> None:
        if self._running.is_set():
            return
        self._running.set()

        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        mgr = socketio.AsyncRedisManager(self.queue_url)
        self.external_sio: socketio.AsyncServer = socketio.AsyncServer(
            client_manager=mgr,
            async_mode="asgi",
            logger=True,
            engineio_logger=True,
            cors_allowed_origins="*",
        )
        self.loop.run_until_complete(self.ws_client())

    def start(self):
        if self._running.is_set():
            return
        super(ContainerAgentThread, self).start()

    def stop(self):
        self._running.clear()
